﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Reflection;

namespace OpenLS.Chart
{
    using ValueChangedEventHandler = EventHandler;

    internal static class WeakEventHelper
    {
        public static EventHandler<TEventArgs> MakeWeak<TEventArgs>(EventHandler<TEventArgs> eventHandler,
                                                                    UnregisterCallback<TEventArgs> unregister)
            where TEventArgs : EventArgs
        {
            Contract.Assert(eventHandler != null);
            Contract.Assert(!(eventHandler.Method.IsStatic || eventHandler.Target == null));

            Type wehType = typeof (WeakEventHandler<,>).MakeGenericType(eventHandler.Method.DeclaringType,
                                                                        typeof (TEventArgs));
            ConstructorInfo wehConstructor = wehType.GetConstructor(new[]
                                                                        {
                                                                            typeof (EventHandler<TEventArgs>),
                                                                            typeof (UnregisterCallback<TEventArgs>)
                                                                        });

            var weh = (IWeakEventHandler<TEventArgs>) wehConstructor.Invoke(
                new object[] {eventHandler, unregister});

            return weh.Handler;
        }

        internal static void AddWeakPropertyChanged(INotifyPropertyChanged owner, PropertyChangedEventHandler value)
        {
            Contract.Requires(value != null);
            INotifyPropertyChanged nic = owner;
            PropertyChangedEventHandler weakEvent = EventHandlerHelper.MakeWeakPropertyChanged(value,
                                                                                               v =>
                                                                                               nic.PropertyChanged -= v);
            nic.PropertyChanged += weakEvent;
        }
        internal static void AddWeakCollectionChanged(INotifyCollectionChanged owner, NotifyCollectionChangedEventHandler value)
        {
            Contract.Requires(value != null);
            INotifyCollectionChanged nic = owner;
            NotifyCollectionChangedEventHandler weakEvent = EventHandlerHelper.MakeWeakCollectionChanged(value,
                                                                                               v =>
                                                                                               nic.CollectionChanged -= v);
            nic.CollectionChanged += weakEvent;
        }
        internal static void AddWeakValueChanged(INotifyValueChanged owner, EventHandler value)
        {
            Contract.Requires(value != null);
            INotifyValueChanged nic = owner;
            EventHandler weakEvent = EventHandlerHelper.MakeWeakValueChanged(value, v => nic.ValueChanged -= v);
            nic.ValueChanged += weakEvent;
        }

        #region Nested type: EventHandlerHelper

        internal static class EventHandlerHelper
        {
            public static EventHandler<TEventArgs> MakeWeak<TEventArgs>(EventHandler<TEventArgs> eventHandler,
                                                                        UnregisterCallback<TEventArgs> unregister)
                where TEventArgs : EventArgs
            {
                if (eventHandler == null)
                    throw new ArgumentNullException("eventHandler");
                if (eventHandler.Method.IsStatic || eventHandler.Target == null)
                    throw new ArgumentException("Only instance methods are supported.", "eventHandler");

                Type wehType = typeof (WeakEventHandler<,>).MakeGenericType(eventHandler.Method.DeclaringType,
                                                                            typeof (TEventArgs));
                ConstructorInfo wehConstructor = wehType.GetConstructor(new[]
                                                                            {
                                                                                typeof (EventHandler<TEventArgs>),
                                                                                typeof (UnregisterCallback<TEventArgs>)
                                                                            });

                var weh = (IWeakEventHandler<TEventArgs>) wehConstructor.Invoke(
                    new object[] {eventHandler, unregister});

                return weh.Handler;
            }

            public static PropertyChangedEventHandler MakeWeakPropertyChanged(PropertyChangedEventHandler eventHandler,
                                                                              UnregisterPropertyChangedCallback
                                                                                  unregister)
            {
                if (eventHandler == null)
                    throw new ArgumentNullException("eventHandler");
                if (eventHandler.Method.IsStatic || eventHandler.Target == null)
                    throw new ArgumentException("Only instance methods are supported.", "eventHandler");

                Type wehType =
                    typeof (WeakPropertyChangedEventHandler<>).MakeGenericType(eventHandler.Method.DeclaringType);
                ConstructorInfo wehConstructor = wehType.GetConstructor(new[]
                                                                            {
                                                                                typeof (PropertyChangedEventHandler),
                                                                                typeof (
                                                                                    UnregisterPropertyChangedCallback)
                                                                            });

                var weh =
                    (IWeakPropertyChangedEventHandler) wehConstructor.Invoke(new object[] {eventHandler, unregister});
                return weh.Handler;
            }

            public static NotifyCollectionChangedEventHandler MakeWeakCollectionChanged(NotifyCollectionChangedEventHandler eventHandler,
                                                                              UnregisterCollectionChangedCallback
                                                                                  unregister)
            {
                if (eventHandler == null)
                    throw new ArgumentNullException("eventHandler");
                if (eventHandler.Method.IsStatic || eventHandler.Target == null)
                    throw new ArgumentException("Only instance methods are supported.", "eventHandler");

                Type wehType =
                    typeof(WeakCollectionChangedEventHandler<>).MakeGenericType(eventHandler.Method.DeclaringType);
                ConstructorInfo wehConstructor = wehType.GetConstructor(new[]
                                                                            {
                                                                                typeof (NotifyCollectionChangedEventHandler),
                                                                                typeof (
                                                                                    UnregisterCollectionChangedCallback)
                                                                            });

                var weh =
                    (IWeakCollectionChangedEventHandler)wehConstructor.Invoke(new object[] { eventHandler, unregister });
                return weh.Handler;
            }

            public static EventHandler MakeWeakValueChanged(EventHandler eventHandler,
                                                            UnregisterValueChangedCallback unregister)
            {
                Contract.Requires(eventHandler != null);
                Contract.Requires(!(eventHandler.Method.IsStatic || eventHandler.Target == null));

                Type wehType = typeof (WeakValueChangedEventHandler<>).MakeGenericType(eventHandler.Method.DeclaringType);
                ConstructorInfo wehConstructor = wehType.GetConstructor(new[]
                                                                            {
                                                                                typeof (EventHandler),
                                                                                typeof (UnregisterValueChangedCallback)
                                                                            });
                Contract.Assert(typeof (EventHandler).IsAssignableFrom(eventHandler.GetType()));
                Contract.Assert(typeof (UnregisterValueChangedCallback).IsAssignableFrom(unregister.GetType()));
                var weh = (IWeakValueChangedEventHandler) wehConstructor.Invoke(new object[] {eventHandler, unregister});
                return weh.Handler;
            }
        }

        #endregion

        #region Nested type: IWeakEventHandler

        private interface IWeakEventHandler<TEventArgs>
            where TEventArgs : EventArgs
        {
            EventHandler<TEventArgs> Handler { get; }
        }

        #endregion

        #region Nested type: IWeakPropertyChangedEventHandler

        private interface IWeakPropertyChangedEventHandler
        {
            PropertyChangedEventHandler Handler { get; }
        }

        private interface IWeakCollectionChangedEventHandler
        {
            NotifyCollectionChangedEventHandler Handler { get; }
        }

        #endregion

        #region Nested type: IWeakValueChangedEventHandler

        internal interface IWeakValueChangedEventHandler
        {
            ValueChangedEventHandler Handler { get; }
        }

        #endregion

        #region Nested type: UnregisterCallback

        internal delegate void UnregisterCallback<TEventArgs>(EventHandler<TEventArgs> eventHandler)
            where TEventArgs : EventArgs;

        #endregion

        #region Nested type: UnregisterPropertyChangedCallback

        internal delegate void UnregisterPropertyChangedCallback(PropertyChangedEventHandler eventHandler);

        internal delegate void UnregisterCollectionChangedCallback(NotifyCollectionChangedEventHandler eventHandler);

        #endregion

        #region Nested type: UnregisterValueChangedCallback

        internal delegate void UnregisterValueChangedCallback(ValueChangedEventHandler eventHandler);

        #endregion

        #region Nested type: WeakEventHandler

        internal class WeakEventHandler<T, TEventArgs> : IWeakEventHandler<TEventArgs>
            where T : class
            where TEventArgs : EventArgs
        {
            private readonly EventHandler<TEventArgs> _handler;
            private readonly OpenEventHandler _openHandler;
            private readonly WeakReference _target;
            private UnregisterCallback<TEventArgs> _unregister;

            public WeakEventHandler(EventHandler<TEventArgs> eventHandler, UnregisterCallback<TEventArgs> unregister)
            {
                _target = new WeakReference(eventHandler.Target);
                _openHandler = (OpenEventHandler) Delegate.CreateDelegate(typeof (OpenEventHandler),
                                                                          null, eventHandler.Method);
                _handler = invoke;
                _unregister = unregister;
            }

            #region IWeakEventHandler<TEventArgs> Members

            public EventHandler<TEventArgs> Handler
            {
                get { return _handler; }
            }

            #endregion

            private void invoke(object sender, TEventArgs eventArgs)
            {
                var target = (T) _target.Target;

                if (target != null)
                    _openHandler(target, sender, eventArgs);
                else if (_unregister != null)
                {
                    _unregister(_handler);
                    _unregister = null;
                }
            }

            public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<T, TEventArgs> weh)
            {
                return weh._handler;
            }

            #region Nested type: OpenEventHandler

            private delegate void OpenEventHandler(T @this, object sender, TEventArgs eventArgs);

            #endregion
        }

        #endregion

        #region Nested type: WeakPropertyChangedEventHandler

        internal class WeakPropertyChangedEventHandler<T> : IWeakPropertyChangedEventHandler where T : class
        {
            private readonly PropertyChangedEventHandler _handler;
            private readonly OpenEventHandler _openHandler;
            private readonly WeakReference _target;
            private UnregisterPropertyChangedCallback _unregister;

            public WeakPropertyChangedEventHandler(PropertyChangedEventHandler eventHandler,
                                                   UnregisterPropertyChangedCallback unregister)
            {
                _target = new WeakReference(eventHandler.Target);
                _openHandler = (OpenEventHandler) Delegate.CreateDelegate(typeof (OpenEventHandler),
                                                                          null, eventHandler.Method);
                _handler = invoke;
                _unregister = unregister;
            }

            #region IWeakPropertyChangedEventHandler Members

            public PropertyChangedEventHandler Handler
            {
                get { return _handler; }
            }

            #endregion

            private void invoke(object sender, PropertyChangedEventArgs eventArgs)
            {
                var target = (T) _target.Target;

                if (target != null)
                    _openHandler.Invoke(target, sender, eventArgs);
                else if (_unregister != null)
                {
                    _unregister(_handler);
                    _unregister = null;
                }
            }

            public static implicit operator PropertyChangedEventHandler(WeakPropertyChangedEventHandler<T> weh)
            {
                return weh._handler;
            }

            #region Nested type: OpenEventHandler

            private delegate void OpenEventHandler(T @this, object sender, PropertyChangedEventArgs eventArgs);

            #endregion
        }
        internal class WeakCollectionChangedEventHandler<T> : IWeakCollectionChangedEventHandler where T : class
        {
            private readonly NotifyCollectionChangedEventHandler _handler;
            private readonly OpenEventHandler _openHandler;
            private readonly WeakReference _target;
            private UnregisterCollectionChangedCallback _unregister;

            public WeakCollectionChangedEventHandler(NotifyCollectionChangedEventHandler eventHandler,
                                                   UnregisterCollectionChangedCallback unregister)
            {
                _target = new WeakReference(eventHandler.Target);
                _openHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler),
                                                                          null, eventHandler.Method);
                _handler = invoke;
                _unregister = unregister;
            }

            #region IWeakPropertyChangedEventHandler Members

            public NotifyCollectionChangedEventHandler Handler
            {
                get { return _handler; }
            }

            #endregion

            private void invoke(object sender, NotifyCollectionChangedEventArgs eventArgs)
            {
                var target = (T)_target.Target;

                if (target != null)
                    _openHandler.Invoke(target, sender, eventArgs);
                else if (_unregister != null)
                {
                    _unregister(_handler);
                    _unregister = null;
                }
            }

            public static implicit operator NotifyCollectionChangedEventHandler(WeakCollectionChangedEventHandler<T> weh)
            {
                return weh._handler;
            }

            #region Nested type: OpenEventHandler

            private delegate void OpenEventHandler(T @this, object sender, NotifyCollectionChangedEventArgs eventArgs);

            #endregion
        }

        #endregion

        #region Nested type: WeakValueChangedEventHandler

        internal class WeakValueChangedEventHandler<T> : IWeakValueChangedEventHandler where T : class
        {
            private readonly ValueChangedEventHandler _handler;
            private readonly OpenEventHandler _openHandler;
            private readonly WeakReference _target;
            private UnregisterValueChangedCallback _unregister;

            public WeakValueChangedEventHandler(ValueChangedEventHandler eventHandler,
                                                UnregisterValueChangedCallback unregister)
            {
                _target = new WeakReference(eventHandler.Target);
                _openHandler = (OpenEventHandler) Delegate.CreateDelegate(typeof (OpenEventHandler),
                                                                          null, eventHandler.Method);
                _handler = invoke;
                _unregister = unregister;
            }

            #region IWeakValueChangedEventHandler Members

            public ValueChangedEventHandler Handler
            {
                get { return _handler; }
            }

            #endregion

            private void invoke(object sender, EventArgs eventArgs)
            {
                var target = (T) _target.Target;

                if (target != null)
                    _openHandler.Invoke(target, sender, eventArgs);
                else if (_unregister != null)
                {
                    _unregister(_handler);
                    _unregister = null;
                }
            }

            public static implicit operator ValueChangedEventHandler(WeakValueChangedEventHandler<T> weh)
            {
                return weh._handler;
            }

            #region Nested type: OpenEventHandler

            private delegate void OpenEventHandler(T @this, object sender, EventArgs eventArgs);

            #endregion
        }

        #endregion
    }
}